//////////////////////////////////////////////////////////////////////////
/// @file RegistryUtils.h
/// @brief Classes for working with registry values
//////////////////////////////////////////////////////////////////////////

#ifndef REGISTRY_VALUE_H
#define REGISTRY_VALUE_H

#ifdef WIN32
	#include <windows.h>
	#include <tchar.h>
#endif

#include <string>
#include <sstream>
#include <iomanip>
#include <assert.h>

#ifdef WIN32

	///
	typedef TCHAR Q_TCHAR;

#else

	/// 
	typedef char Q_TCHAR;

	/// Typedef for Linux systems
	typedef const int * HKEY;

	/// Constant for Linux systems. Means $(HOME)/company/etc/ folder
	int const * const HKEY_CURRENT_USER  = (int *)0x1;

	/// Constant for Linux systems. Means /opt/company/etc/ folder
	int const * const HKEY_LOCAL_MACHINE = (int *)0x2;

	/// Constant for Linux systems.
	const int ERROR_SUCCESS = 0x0;

	/// Constant for Linux systems.
	const int REG_DWORD = 0x1;

	/// Constant for Linux systems.
	const int REG_SZ = 0x2;

	/// Constant for Linux systems.
	const int REG_BINARY = 0x3;

#endif

/// Current User root key
#define HKCU HKEY_CURRENT_USER

/// Local Machine root key
#define HKLM HKEY_LOCAL_MACHINE

/// Special typedef for UNICODE builds
#ifdef UNICODE
	typedef std::wstringstream std_stringstream;
	typedef std::wstring std_string;
#else
	typedef std::stringstream std_stringstream;
	typedef std::string std_string;
#endif

// Usage:

// Public typedefs declared at the end of this header
// typedef registry_value REG;
// typedef registry_string<std_string>	CRegString;
// typedef registry_int<DWORD>          CRegDWORD;
// typedef registry_int<INT>            CRegInt;
// typedef registry_int<bool>           CRegBool;
// typedef registry_string<double>      CRegDouble;
// typedef registry_binary<POINT>       CRegPoint;
// typedef registry_binary<RECT>        CRegRect;

// Typical constructor:
// CRegString( _T("Software/CompanyName/SubKey/Value"), HKLM or HKCU, <default value>, unsigned short a_wOptions = RF_NONE )
// or
// CRegString( HKLM or HKCU, _T("Software/CompanyName/SubKey/Value"), <default value>, unsigned short a_wOptions = RF_NONE )

/// Deprecated defines
#define USE_REG_STRING
#define USE_REG_DWORD
#define USE_REG_INT
#define USE_REG_BOOL
#define USE_REG_DOUBLE
#define USE_REG_POINT
#define USE_REG_RECT
#define USE_STD_TYPES

///
/// @class registry_value
///
/// Define basic registry operations not connected to the concrete data type
///
class registry_value
{
	public:

		/// Registry value optional flags, unsigned short bit masks
		enum
		{
			RF_NONE			= 0x0000, ///< No flags set
			RF_FORCE		= 0x0001, ///< Read and write the value directly through the registry
			RF_READONLY		= 0x0002, ///< The registry value cannot be changed by the program
			RF_FORCE_WRITE	= 0x0004  ///< Read and write the value directly through the registy, even if default has not been changed.
		};

		/// constructor. it gets name of the key and base key
		registry_value( const std_string & name, HKEY base_, unsigned short a_wOptions = RF_NONE )
			: key(NULL), base(base_), m_wOptions(a_wOptions)
		{
			make_path( name );
		}

		/// constructor. it gets name of the key and base key
		registry_value( HKEY base_, const std_string & name, unsigned short a_wOptions = RF_NONE )
			: key(NULL), base(base_), m_wOptions(a_wOptions)
		{
			make_path( name );
		}

		/// constructor. it gets the path of the key, and the key itself, and base key
		registry_value( const std_string & key_path, const std_string & value_name,
			HKEY base_, unsigned short a_wOptions = RF_NONE )
			: key(NULL), base(base_), m_wOptions(a_wOptions)
		{
			make_path( key_path, value_name );
		}

		/// constructor. it gets the path of the key, and the key itself, and base key
		registry_value( HKEY base_, const std_string & key_path,
			const std_string & value_name, unsigned short a_wOptions = RF_NONE )
			: key(NULL), base(base_), m_wOptions(a_wOptions)
		{
			make_path( key_path, value_name );
		}

		/// Destructor
		virtual ~registry_value() {}

		/// Test if the registry value exists
		bool exists()
		{
			bool returnval = false;
			bool was_opened = (key != NULL);

			if( was_opened || open( false ) )
			{
				unsigned long type, size;
				returnval = query_value( &type, &size, NULL );
				if( !was_opened )
					close();
			}

			return returnval;
		}

		/// Remove registry value
		void remove_value( void )
		{
			bool was_opened = (key != NULL);
			if( was_opened || open(true) )
			{
				#ifdef WIN32
				RegDeleteValue( key, valuename.c_str() );
				#endif
				if( !was_opened )
					close();
			}
		}

		/// Remove registry key
		void remove_key( void )
		{
			#ifdef WIN32
			RegDeleteKey( base, keyname.c_str() );
			#endif
		}

	protected:

		/// unsigned short bit mask for the registry value optional flags
		unsigned short m_wOptions;

		/// Open the registry key
		bool open( bool write_access )
		{
			assert(key == NULL);

			if( write_access == true )
			{
				#ifdef WIN32
				unsigned long disposition;
				if( RegCreateKeyEx( base, keyname.c_str(), NULL, _T(""), REG_OPTION_NON_VOLATILE,
					KEY_WRITE | KEY_READ, NULL, &key, &disposition) == ERROR_SUCCESS )
					return true;
				#endif
			}

			#ifdef WIN32
			if( RegOpenKeyEx( base, keyname.c_str(), NULL, KEY_READ, &key ) == ERROR_SUCCESS )
				return true;
			#endif

			return false;
		}

		/// Close the registry key
		void close( void )
		{
			assert( key != NULL );
			#ifdef WIN32
			RegCloseKey( key );
			#endif
			key = NULL;
		}

		/// Get the value from the registry
		bool query_value( unsigned long * type, unsigned long * size, void * buffer )
		{
			assert( key != NULL );
			long lRet = -1;
			#ifdef WIN32
			lRet = RegQueryValueEx( key, valuename.c_str(), NULL, type, reinterpret_cast<BYTE*>(buffer), size );
			#endif
			return (lRet == ERROR_SUCCESS);
		}

		/// Set the value in the registry
		bool set_value( unsigned long type, unsigned long size, const void * buffer )
		{
			assert( key != NULL );
			long lRet = -1;
			#ifdef WIN32
			lRet = RegSetValueEx( key, valuename.c_str(), NULL, type, reinterpret_cast<const BYTE *>(buffer), size );
			#endif
			return (lRet == ERROR_SUCCESS);
		}

		/// Return true if the registry value is read only
		bool isReadOnly( void )
		{
			return (0 != (m_wOptions & RF_READONLY));
		}

		/// Return true if the registry value is read and saved directly through the registry
		bool isForced( void )
		{
			return (0 != (m_wOptions & RF_FORCE) || 0 != (m_wOptions & RF_FORCE_WRITE));
		}

	private:

		/// Make path from the values
		void make_path( const std_string & name )
		{
			std_string::size_type pos = name.find_last_of('\\');
			assert( pos != std_string::npos );
            keyname = name.substr( 0, pos );
			valuename = name.substr( pos + 1 );
		}

		void make_path( const std_string & a_keyname, const std_string & a_valuename )
		{
            keyname = a_keyname;
			valuename = a_valuename;
		}

		HKEY key;
		HKEY base;

		std_string valuename;
		std_string keyname;
};

///
/// @class registry_int
///
/// Define <unsigned long> and all int data types basic class.
///
template<class T>
class registry_int : public registry_value
{
protected:

	/// Load the value from the registry and returns if it is loaded successfully.
	bool IntLoad( void )
	{
		unsigned long type, size = sizeof(unsigned long), returnval = 0;
		if( (query_value( &type, &size, &returnval ) != true) || (type != REG_DWORD) )
			return false;
		// disable warning C4800: 'unsigned long' : forcing value to bool 'true' or 'false' (performance warning)
		// this is required for convertion from UINT to bool (CRegBool class)
		#pragma warning( disable : 4800 )
		m_Value = T(returnval);
		// enable warning C4800: 'unsigned long' : forcing value to bool 'true' or 'false' (performance warning)
		#pragma warning( default : 4800 )
		return true;
	}

	/// Save the value to the registry and returns if it is saved successfully.
	bool IntSave( void )
	{
		unsigned long data( m_Value );
		if( set_value( REG_DWORD, sizeof(unsigned long), &data ) )
			return true;
		return false;
	}

public:

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_int( const std_string & name, HKEY base, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( name, base, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_int( HKEY base, const std_string & name, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( base, name, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_int( const std_string & key_path,  const std_string & value_name,
		HKEY base, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( key_path, value_name, base, a_wOptions ),
		m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_int( HKEY base, const std_string & key_path, const std_string & value_name,
		T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( base, key_path, value_name, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Save the value in the registry if it is not saved yet
	~registry_int()
	{
		if( !isForced() && !isReadOnly() )
			Save();
	}

	/// Load the value from the registry if necessary and return it.
	operator T()
	{
		if( isForced() )
			Load();
		return m_Value;
	}

	/// Save the value to the internal variable and if Force, to registry.
	const registry_int & operator=(const T & value)
	{
		assert( false == isReadOnly() );
		if( (m_wOptions & RF_FORCE_WRITE) && !isReadOnly() || ((value != m_Value) ))
		{
			m_Value = value;
			if( isForced() )
				Save();
		}
		return *this;
	}

protected:

	/// Current value
	T m_Value;

private:

	/// Load the value from the registry or assign the default value.
	/// Return true if the value was loaded from registry and
	/// false if the default value was assigned.
	bool Load( void )
	{
		bool bRet = false;
		if( open(false) == true )
		{
			bRet = IntLoad();
			close();
		}
		if( ! bRet )
			m_Value = m_DefaultValue;
		return bRet;
	}

	/// Save the value to the registry if it exists or differs from the default.
	bool Save( void )
	{
		bool bRet = false;
		if( open(true) == true )
		{
			if( (m_Value != m_DefaultValue) || (true == exists()) || (m_wOptions & RF_FORCE_WRITE))
				bRet = IntSave();
			close();
		}
		return bRet;
	}

private:

	/// Default value
	T m_DefaultValue;

};


///
/// @class std_stringstream_
///
/// Utility class for redeclaration stringstream operator >>
///
class std_stringstream_: public std_stringstream
{
};

///
/// Redeclaration of stringstream operator >> for fixing issue with
/// getting string with spaces
///

inline std_stringstream_ & operator>>(std_stringstream_ & s, std_string & str)
{
	std::getline(s, str);
	return s;
}

///
/// @class registry_string
///
/// Define std::string and all string-compatible data types basic class.
///
template<class T>
class registry_string : public registry_value
{
protected:

	/// Load the value from the registry and returns if it is loaded successfully.
	bool IntLoad( void )
	{
		bool bRet = false;
		unsigned long type, size;
		if( (query_value(&type, &size, NULL) != true) || (type != REG_SZ) )
			return false;
		Q_TCHAR * data = new Q_TCHAR[size];
		if( query_value( NULL, &size, data ) == true )
		{
			if(size!=0)
			{
				std_stringstream_ ss;
				ss.str( data );
				ss >> m_Value;
			}
			else
				m_Value = _T('\0');
			bRet = true;
		}
		delete [] data;
		return bRet;
	}

	/// Save the value to the registry and returns if it is saved successfully.
	bool IntSave( void )
	{
		std_stringstream ss;
		ss << std::setprecision(20) << m_Value;
		std_string data = ss.str();
		return set_value( REG_SZ, (data.length() + 1) * sizeof(Q_TCHAR), data.c_str() );
	}

public:

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_string( const std_string & name, HKEY base, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( name, base, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_string( HKEY base, const std_string & name, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( base, name, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_string( const std_string & key_path, const std_string & value_name,
		HKEY base, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( key_path, value_name, base, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_string( HKEY base, const std_string & key_path, const std_string & value_name,
		T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( key_path, value_name, base, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Save the value in the registry if it is not saved yet
	~registry_string()
	{
		if( !isForced() && !isReadOnly() )
			Save();
	}

	/// Load the value from the registry if necessary and return it.
	operator T()
	{
		if( isForced() )
			Load();
		return m_Value;
	}

	/// Save the value to the internal variable and if Force, to registry.
	const registry_string & operator=(const T & value)
	{
		assert( false == isReadOnly() );
		if( (m_wOptions & RF_FORCE_WRITE) && !isReadOnly() || ((value != m_Value) ))
		{
			m_Value = value;
			if( isForced() )
				Save();
		}
		return *this;
	}

protected:

	/// Current value
	T m_Value;

private:

	/// Load the value from the registry or assign the default value.
	/// Return true if the value was loaded from registry and
	/// false if the default value was assigned.
	bool Load( void )
	{
		bool bRet = false;
		if( open(false) == true )
		{
			bRet = IntLoad();
			close();
		}
		if( ! bRet )
			m_Value = m_DefaultValue;
		return bRet;
	}

	/// Save the value to the registry if it exists or differs from the default.
	bool Save( void )
	{
		bool bRet = false;
		if( open(true) == true )
		{
			if( (m_Value != m_DefaultValue) || (true == exists())  || (m_wOptions & RF_FORCE_WRITE))
				bRet = IntSave();
			close();
		}
		return bRet;
	}

private:

	/// Default value
	T m_DefaultValue;

};

///
/// @class registry_binary
///
/// Define binary registry class for all not-standard types.
///
template<class T>
class registry_binary : public registry_value
{
protected:

	/// Load the value from the registry and returns if it is loaded successfully.
	bool IntLoad( void )
	{
		unsigned long type, size = sizeof(T);
		return (query_value( &type, &size, &m_Value ) && (type == REG_BINARY));
	}

	/// Save the value to the registry and returns if it is saved successfully.
	bool IntSave( void )
	{
		return set_value( REG_BINARY, sizeof(T), &m_Value );
	}

public:

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_binary( const std::string & name, HKEY base, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( name, base, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_binary( HKEY base, const std::string & name, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( base, name, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_binary( const std::string & key_path, const std::string & value_name,
		HKEY base, T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( key_path, value_name, base, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Get all parameters and load the value from the registry.
	/// If Force is true, the value is not loaded - it will be loaded each time it is requested.
	registry_binary( HKEY base, const std::string & key_path, const std::string & value_name,
		T a_defValue, unsigned short a_wOptions = RF_NONE )
		: registry_value( base, key_path, value_name, a_wOptions ), m_DefaultValue( a_defValue )
	{
		if( !isForced() )
			Load();
	}

	/// Save the value in the registry if it is not saved yet
	~registry_binary()
	{
		if( !isForced() && !isReadOnly() )
			Save();
	}

	/// Load the value from the registry if necessary and return it.
	operator T()
	{
		if( isForced() )
			Load();
		return m_Value;
	}

	/// Save the value to the internal variable and if Force, to registry.
	const registry_binary & operator=(const T & value)
	{
		assert( false == isReadOnly() );
		if( (m_wOptions & RF_FORCE_WRITE) && !isReadOnly() || ((value != m_Value) ))
		{
			m_Value = value;
			if( isForced() )
				Save();
		}
		return *this;
	}

protected:

	/// Current value
	T m_Value;

private:

	/// Load the value from the registry or assign the default value.
	/// Return true if the value was loaded from registry and
	/// false if the default value was assigned.
	bool Load( void )
	{
		bool bRet = false;
		if( open(false) == true )
		{
			bRet = IntLoad();
			close();
		}
		if( ! bRet )
			m_Value = m_DefaultValue;
		return bRet;
	}

	/// Save the value to the registry if it exists or differs from the default.
	bool Save( void )
	{
		bool bRet = false;
		if( open(true) == true )
		{
			if( (m_Value != m_DefaultValue) || (true == exists())  || (m_wOptions & RF_FORCE_WRITE))
				bRet = IntSave();
			close();
		}
		return bRet;
	}

private:

	/// Default value
	T m_DefaultValue;

};

/*
	This was a class without streams to work with strings - more effective.
	I remove it since it does not seems to be important to me.
	Andrew S. Jan 1, 2003

template<>
class registry_string<std::string> : public registry_value
{
	public:
		registry_string(const std::string & name, HKEY base) : registry_value(name, base)
		{
		}

		operator std::string()
		{
			std::string returnval;

			if(open(false) == true)
			{
				unsigned long type, size;
				if(query_value(&type, &size, 0) == true)
				{
					assert(type == REG_SZ);
					char * data = new char[size];

					if(query_value(0, &size, data) == true)
						returnval = data;

					delete [] data;
				}

				close();
			}

			return returnval;
		}

		const registry_string & operator=(const std::string & value)
		{
			if(open(true) == true)
			{
				set_value(REG_SZ, value.length() + 1, value.c_str());
				close();
			}

            return *this;
		}
};
*/

//////////////////////////////////////////////////////////////////////////////////////////////////////
// Typedefs
//////////////////////////////////////////////////////////////////////////////////////////////////////

typedef registry_string<std_string>	CRegString;

typedef registry_int<unsigned long> CRegDWORD;

typedef registry_int<long int> CRegInt;

typedef registry_int<bool> CRegBool;

typedef registry_string<double> CRegDouble;

#ifdef WIN32

	typedef registry_binary<POINT> CRegPoint;

	typedef registry_binary<RECT> CRegRect;

#endif

/// Special typedef solely for easier specifying RF_FORCE and RF_READONLY constants
typedef registry_value REG;


#endif
